// @(#)heart.cc
// Time-stamp: <2009-02-13 21:30:57 root>
// Copyright 2008 Julian Qian
// Author: root@localhost.localdomain
// Version: $Id: heart.cc,v 0.0 2008/09/27 13:55:33 root Exp $
// Keywords: 

#include "log.hh"
#include "mutex.hh"
#include "db.hh"
#include "soap/wrapesms.hh"
#include "func.hh"
#include "excep.hh"
// #include "proto.hh"

#include "heart.hh"

#include <string>
#include <iostream>

// using namespace std;

// create hash_map

// travel hash_map every 5 min, check expired and alarm

// search hash_map when recv an udp, otherwise query database

Heart* Heart::_theHeart = NULL;

void
Heart::initHeart(HeartMap* hmap){
    if(NULL == _theHeart){
        _theHeart = new Heart(hmap);
    }else{
        throw HeartEngineFailed("init heart");
    }
}

Heart*
Heart::theHeart(){
    if(NULL == _theHeart){
        throw HeartEngineFailed("the heart");
    }
    
    return _theHeart;
}

void
Heart::endHeart(){
    delete _theHeart;
}

Heart::Heart(HeartMap* hmap){
    _hMap = hmap;

    try{
        _db = new Db;
    }
    catch(const mysqlpp::ConnectionFailed& ex){
        throw HeartEngineFailed("heart ctor");
    }    
    try{
        _we = Wrapesms::theWrapesms();
    }
    catch(const ConfigError& ex){
        throw HeartEngineFailed("Esms Config Error");
    }
    catch(const EsmsDisabled &ex){
        LInfo(ex.what());
    }

    // _ventricle = new HEART_PAIR;
}

Heart::~Heart(){
    // delete _ventricle;
    // shared_memory_object::remove(SHM_HEART_SEG);
    delete _db;
}


// void
// Heart::runloop(){
//     _ds->engine(dispatcher);
// }

// void
// Heart::dispatcher(int signo){
//     Thread thd;

//     thd.run((void * (*) (void *))active_heart, NULL);
// }

int
Heart::get_lognum(devid_t did){
    int lognum = 0;
    try{
        mysqlpp::Connection* conn = _db->con();
        // last 10 status
        mysqlpp::Query query = conn->query();
        query << "select count(devid) cnt from status where devid = " << did << ";";

        LDebug(query.str());
                
        if(mysqlpp::StoreQueryResult res = query.store()){
            lognum = (int)res[0]["cnt"];
        }
        _db->end(conn);
    }
    catch(const mysqlpp::BadQuery &er){
        LFatal( "Search status failed! " << er.what() );
        lognum = -1;
    }
    return lognum;
}

// Only one functions to call it Sever::servo()
void
Heart::push_heart(Proto& pro){
    
    devid_t key_devid = pro.get_devid();
    
    bool authed = pro.is_auth(); // do not access database if no authorised

    // authed? LDebug("Auth OK!"): LDebug("Auth Failed!");
        HeartMap::iterator itr;
        itr = _hMap->find(key_devid);
        if( itr == _hMap->end()){
            // 1) a new device online
            Proto value_proto(pro);
            LDebug("one new Device (" << value_proto.get_devid() <<") was pushed, alarm:" << value_proto.get_alarm());
            // _hMap->insert(HeartMap::value_type(key_devid, value_proto));
            _hMap->insert(HPPair(key_devid, value_proto));

            if(authed){
                // we only do insert action, in order to AVOID another spare
                // count action to database
                dblog_record(value_proto);
            }
        
        }else{
            // 2) status/ip changed -> need update db
            // 3) up_time, sequence changed -> only need update hash table

            if(authed){
                if(!pro.equal(itr->second) && !pro.is_omit()){
                    if(pro.is_omit()){
                        LDebug("timerrrrr");
                    }
                    // LDebug("Device status changed from " << (itr->second).get_stat() << " to " << pro.get_stat());
                    // update db if status changed. BUT if timer message, omit it.
                    dblog_record(pro);
                }
            }
        
            (itr->second).copy(pro);
            // itr->second  = pro;
        }
        // update database

        LDebug("Proto NUM: "<< proto_nums());
    
}

void
Heart::dblog_record(Proto& pro){
    mysqlpp::Connection::thread_start();
    try{
        mysqlpp::Connection* conn = _db->con();
        mysqlpp::Query query = conn->query();
        query << "insert into status (devid,ip,stat,up_time) values (" << pro.get_devid() << "," << pro.get_ip() << "," << pro.get_stat() << "," << pro.get_up_time() << ");";

        LDebug(query.str());
                    
        query.exec();
        _db->end(conn);
    }
    catch(const mysqlpp::BadQuery &er){
        LFatal( "Insert devid log failed: "<< pro.get_devid() << ": " << er.what());
    }
    mysqlpp::Connection::thread_end();    
}

void
Heart::dblog_online(devid_t devid){
    
    try{
        mysqlpp::Connection* conn = _db->con();
        mysqlpp::Query query = conn->query();
        query << "update user set on_time=UNIX_TIMESTAMP() where devid=" << devid;

        LDebug(query.str());
                    
        query.exec();
        _db->end(conn);
    }
    catch(const mysqlpp::BadQuery &er){
        LFatal( "update device logon timestamp failed: "<< devid << ": " << er.what());
    }
}

void
Heart::dblog_offline(devid_t devid){
    
    try{
        // std::string sql = "update user set off_time=UNIX_TIMESTAMP() where devid=";
        // sql += itoa(devid);
        // sql += ";";
        
        std::string sql = "insert into status (devid,ip,up_time,stat) values (";
        sql += itoa(devid);
        sql += ", ";
        sql += itoa(get_ip(devid));
        sql += ", UNIX_TIMESTAMP(), ";
        sql += itoa(STAT_OFFLI);
        sql += ");";
        
        mysqlpp::Connection* conn = _db->con();
        mysqlpp::Query query = conn->query(sql);
        // query << "update user set off_time=UNIX_TIMESTAMP() where devid=" << devid;

        LDebug(query.str());
                    
        query.exec();
        _db->end(conn);
    }
    catch(const mysqlpp::BadQuery &er){
        LFatal( "update device logoff timestamp failed: "<< devid << ": " << er.what());
    }
}

// travel heart, and delete inactive items (expired)
void
Heart::active_heart(){
    // LDebug("active heart loop ...");

        // devid_t key_devid = 1;
        // char vp[17] = "0123456789012345";
        // Proto value_proto(vp, 123, true);
        // _hMap->insert(HPPair(key_devid, value_proto));

        LDebug("Heart Nums ==>" << _hMap->size());
    

        // float tmp = mymap->find(10)->second;
        // std::cout << "mymap 10th is " << tmp << std::endl;

        // segment.destroy<MyMap>("MyMap");
    

        if(_hMap->empty()) return;

        HeartMap::iterator itr;

        int expired_num = 0;
    
        std::string sql = "select mobile, devid, warning from phone where devid IN (";


        // travel heart hash table
        for(itr = _hMap->begin(); itr != _hMap->end(); ){
            // Being expired more than 15*60 secs means inactive, and remove them
            if((itr->second).expired()){
                LInfo("Expire device: " << (itr->second).get_devid());

                sql += itoa(itr->first);
                sql += ",";

                dblog_offline(itr->first);
            
                // delete itr->second;
                _hMap->erase(itr++);
                expired_num ++;

            }else{
                LDebug("Check device stat: " << (itr->second).get_devid());
                itr++;
            }
        }
        

        sql += "0);";

        if(expired_num > 0){
            // send phone alarm
            try{
                mysqlpp::Connection* conn = _db->con();
                mysqlpp::Query query = conn->query(sql);
        
                LDebug("--->" << query.str() << "<---");
        
                mysqlpp::StoreQueryResult res = query.store();
                if(res){
                    std::string mobile;
                    std::string warning = "[REMIND]Your device is offline!";
                    int type;
                    devid_t devid;
                    for(size_t i = 0; i < res.num_rows(); i++){
                        devid = (devid_t)res[i]["devid"];
                        mobile = (std::string)res[i]["mobile"];
                    
                        LDebug("remind mobile:"<< mobile);
                    
                        _we->queue_msg(mobile, warning);
                    }
                    try{
                        _we->putMsg();
                    }
                    catch(const PutMsgFailed& ex){
                        LFatal(ex.what());
                    }                    
                    catch(const EsmsDisabled& ex){
                        
                    }
                }

                _db->end(conn);
            }
    
            catch(const mysqlpp::BadQuery& er){
                LFatal( "Fetch remind phone numbers from db failed!" );
            }
        }

}

// This function is used for a run-loop, which will clean our temporary
// database and delete inactive db records every interval time
void
Heart::clean_heart(){
    try{
        mysqlpp::Connection* conn = _db->con();

        mysqlpp::Query query = conn->query("delete from status where UNIX_TIMESTAMP() - up_time > 60*60*24*7;");  // delete client deactive more than 1 week
        
        query.exec();
        _db->end(conn);
    }
    catch(const mysqlpp::BadQuery& er){
        LFatal( "Clean status in db failed!" );
    }
}

Proto*
Heart::get_proto(devid_t devid){
    // return _ventricle[devid];
    // HeartMap::const_iterator itr;
        // float tmp = mymap->find(10)->second;
        // std::cout << "mymap 10th is " << tmp << std::endl;

        // segment.destroy<MyMap>("MyMap");
    
        HeartMap::iterator itr;
        LDebug("proto num : " << _hMap->size() << ", devid:" <<devid);
        itr = _hMap->find(devid);
        if(itr == _hMap->end()){
            // throw GetProtoError("proto null");
            return NULL;
        }else{
            return &(itr->second);
        }

}

int
Heart::proto_nums(){

        return _hMap->size();

}

ip_t
Heart::get_ip(devid_t devid){
    try{
        
        Proto* pro = get_proto(devid);
        if(NULL != pro){
            return pro->get_ip();
        }
    }catch(...){
    }
    return 0;
    
}


/*
bool
Heart::check_proto(devid_t devid){
    // HeartMap* hmap = shm_get_heart();
    // HeartMap::const_iterator itr = hmap->find(devid);
    // // HeartMap::iterator itr = hmap->find(devid);    
    // if(itr != hmap->end()){
    //     return true;
    // }else{
    //     return false;
    // }
    try{
        BI::mamaged_shared_momory segment(open_only, SHM_HEART_SEG);
        HeartMap* hmap = segment.find<HeartMap>(SHM_HEART_OBJ).first;

        HeartMap::iterator itr = hmap->find(devid);

        if(itr != hmap->end()){
            return true;
        }
    }catch(...){
    }
    return false;    
}
*/

alarm_t
Heart::get_last_alarm_db(devid_t devid){
    alarm_t alaret = CTRL_DEFEN;
    try{
        mysqlpp::Connection* conn = _db->con();
        mysqlpp::Query query = conn->query();
        
        query << "select stat from status where devid=" << devid <<" order by up_time desc limit 1";
        
        LDebug(query.str());
        
        mysqlpp::StoreQueryResult res = query.store();
        if(res){
            stat_t tmp_stat = (stat_t)res[0]["stat"];
            switch(tmp_stat){
            case STAT_DEFEN:
                alaret = CTRL_DEFEN; break;
            case STAT_UNDEF:
                alaret = CTRL_UNDEF; break;
            default:
                alaret = CTRL_NULL; break;
            }
        }
        _db->end(conn);
    }
    catch(const mysqlpp::BadQuery &er){
        LFatal("Search Rest Status failed: "<< er.what());
    }
    return alaret;
}

bool
Heart::auth_db(devid_t devid){
    try{
        mysqlpp::Connection* conn = _db->con();
        mysqlpp::Query query = conn->query();
        
        // query << "select uid from user where locked=0 and devid=" << _devid <<" limit 1";
        query << "select uid from phone where locked=0 and devid=" << devid << " limit 1";
        
        LDebug(query.str());
        
        mysqlpp::SimpleResult res = query.execute();
        int cnt = res.rows();
        _db->end(conn);

        return cnt? true: false;
    }
    catch(const mysqlpp::BadQuery &er){
        return false;
    }
}

stat_t
Heart::check_proto(devid_t devid){
    // HeartMap* hmap = shm_get_heart();
    // HeartMap::const_iterator itr = hmap->find(devid);
    // // HeartMap::iterator itr = hmap->find(devid);    
    // if(itr != hmap->end()){
    //     return true;
    // }else{
    //     return false;
    // }
    using namespace boost::interprocess;
    
    try{
        managed_shared_memory segment(open_only, SHM_HEART_SEG);
        HeartMap* hmap = segment.find<HeartMap>(SHM_HEART_OBJ).first;

        HeartMap::iterator itr = hmap->find(devid);

        if(itr != hmap->end()){
            
            Proto& pro = itr->second;
            return pro.get_stat();
        }
    }catch(...){
        LInfo("shared segment failed!");
    }
    return STAT_NULLL;    
}

void
Heart::print_protos(){
    using namespace boost::interprocess;
    using std::cout;
    using std::setw;
    using std::setfill;
    using std::endl;
    
    try{
        managed_shared_memory segment(open_only, SHM_HEART_SEG);
        HeartMap* hmap = segment.find<HeartMap>(SHM_HEART_OBJ).first;

        HeartMap::iterator itr;

        cout << setw(8) << setfill(' ') << "Dev ID" \
             << setw(3) << setfill(' ') << "ST"     \
             << setw(3) << setfill(' ') << "AM"      \
             << setw(16) << setfill(' ') << "IP Address" \
             << setw(20) << setfill(' ') << "Up Time" << endl;
            
        for(itr = hmap->begin(); itr != hmap->end(); ++itr){
            Proto& pro = itr->second;
            if(pro.is_auth()){
                cout << setw(8) << setfill(' ') << pro.get_devid() ;
            }else{
                cout << setw(8) << setfill('!') << pro.get_devid() ;
            }
            cout << setw(3) << setfill(' ') << pro.get_stat()        \
                 << setw(3) << setfill(' ') << pro.get_alarm()       \
                 << setw(16) << setfill(' ') << IP(pro.get_ip()) \
                 << setw(20) << setfill(' ') << timestr(pro.get_up_time()) << endl;
        }
        
    }catch(...){
        LInfo("fetch shared segment failed!");
    }
}
